ci: replace cpp-linter-action with cpp-linter CLI#838
Conversation
cpp-linter/cpp-linter-action is frozen at v2.15.1: v2.16+ introduced an untrusted dependency and was blocked by ASF Infra (apache/infrastructure-actions#325), and the ASF gateway ignores newer versions. The pinned action still runs but can no longer be updated. The action is a thin wrapper around the cpp-linter PyPI package, which is not affected by the block. Install that package plus matching clang tools with pip and call the cpp-linter CLI with the same options the action used, keeping the checks-failed output and the Fail fast step. This is a run: step rather than a uses: reference, so it adds no new third-party action to the ASF allowlist. Fixes apache#336.
There was a problem hiding this comment.
Pull request overview
This PR updates the C++ linter workflow to stop using the frozen cpp-linter/cpp-linter-action wrapper and instead install and run the cpp-linter PyPI CLI directly, keeping the existing “checks-failed” / fail-fast behavior while avoiding adding a new third‑party action reference.
Changes:
- Replace
cpp-linter/cpp-linter-actionwith asetup-python+pip install+cpp-linterCLI invocation in the CI workflow. - Add a temporary probe C++ source file intended to intentionally trigger
clang-tidy/naming warnings to exercise the new linter path.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/iceberg/temp_linter_probe.cc |
Adds a temporary intentionally-noncompliant file to force the linter to report findings. |
.github/workflows/cpp-linter.yml |
Switches from the GitHub Action wrapper to installing and running the cpp-linter CLI with equivalent arguments. |
| // TEMP verification file — will be dropped before merge. | ||
| // Intentionally violates .clang-tidy to confirm the new cpp-linter CLI runs. |
There was a problem hiding this comment.
This is a temporary test for lint validation and will be deleted after testing.
| - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | ||
| if: github.event_name == 'pull_request' | ||
| - name: Install cpp-linter and clang tools | ||
| if: github.event_name == 'pull_request' | ||
| run: pip install "cpp-linter==1.13.0" "clang-format==22.1.8" "clang-tidy==22.1.8" |
There was a problem hiding this comment.
Good catch, done. Pinned setup-python to 3.13 and switched to python -m pip install so it uses the interpreter the action set up. I kept the run step as cpp-linter (its console entry point) since the package has no __main__, so it can't be called with python -m; it's installed into the same interpreter, so they stay in sync.
7889803 to
88d5abc
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
.github/workflows/cpp-linter.yml:98
actions/setup-pythonis invoked without specifyingpython-version, which makes the job dependent on the runner’s preinstalled Python (and whichpipit provides). That can change over time and can also result inpipresolving to a different interpreter than the onesetup-pythonput on PATH. Specify a Python version (consistent with other workflows) and usepython -m pipfor the installs.
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
if: github.event_name == 'pull_request'
- name: Install cpp-linter and clang tools
if: github.event_name == 'pull_request'
run: pip install "cpp-linter==1.13.0" "clang-format==22.1.8" "clang-tidy==22.1.8"
Address review feedback: pin setup-python to 3.13 so the runner's default Python cannot drift, and install with python -m pip so it targets the interpreter the action set up rather than a bare pip on PATH.
|
cc @wgtmac |
wgtmac
left a comment
There was a problem hiding this comment.
Nice job! Thanks a lot, @LuciferYang!
| python-version: '3.13' | ||
| - name: Install cpp-linter and clang tools | ||
| if: github.event_name == 'pull_request' | ||
| run: python -m pip install "cpp-linter==1.13.0" "clang-format==22.1.8" "clang-tidy==22.1.8" |
There was a problem hiding this comment.
Should we parameterize these versions somewhere?
There was a problem hiding this comment.
Done. The versions now sit in the job's env block (CPP_LINTER_VERSION, CLANG_TOOLS_VERSION), and --version takes its major from ${CLANG_TOOLS_VERSION%%.*}, so the clang version is only written once.
I left one thing out: .pre-commit-config.yaml pins its own clang-format (v22.1.5), so it and the CI job aren't sharing a source. Actions has no way to read a version across files without extra tooling, so I didn't want to widen this PR for it. I can open a separate issue if aligning them is worth it.
There was a problem hiding this comment.
Since .pre-commit-config.yaml pins clang-format to 22.1.5, shouldn't we use the same version here?
There was a problem hiding this comment.
Let me test this out.
There was a problem hiding this comment.
Good point on aligning. One wrinkle: clang-format 22.1.5 is on PyPI, but clang-tidy skips it (only 22.1.0, 22.1.0.1, 22.1.7, and 22.1.8 are published), so I can't pin both to 22.1.5 from a single variable without breaking the tidy install.
Two ways to handle it: split into CLANG_FORMAT_VERSION (22.1.5, matching pre-commit) and CLANG_TIDY_VERSION (nearest available, 22.1.8); or bump pre-commit's clang-format to 22.1.8 so all three land on one version. pre-commit only runs clang-format here, not clang-tidy, so the alignment really only concerns the format version. Which do you prefer?
There was a problem hiding this comment.
Sorry I clicked the suggestion by accident, now I've reverted it.
There was a problem hiding this comment.
I would prefer decoupling them into CLANG_FORMAT_VERSION and CLANG_TIDY_VERSION first because they may diverge in the future anyway. I'm fine to choose whatever version. We need to add a comment on either side to notify they should be in sync. WDYT?
There was a problem hiding this comment.
Split them as you suggested: CLANG_FORMAT_VERSION (22.1.5, matching pre-commit) and CLANG_TIDY_VERSION (22.1.8, since clang-tidy has no 22.1.5 on PyPI). Added a comment on the format version pointing at .pre-commit-config.yaml as the sync reminder.
There was a problem hiding this comment.
Before we settle on 22.1.5, one thing I hit while testing the pin. Running clang-format 22.1.5 (the version pre-commit pins) over the current tree, 4 files come back as non-conforming: catalog/rest/error_handlers.cc, error_handlers.h, test/error_handlers_test.cc, and util/error_collector.h. They pass under 22.1.8 (what CI runs today), but 22.1.5 wants to reformat them.
So the tree is effectively formatted to 22.1.8 right now, while pre-commit declares 22.1.5. The two are already out of sync. If CI's clang-format goes to 22.1.5, any later PR touching those files would fail the linter on formatting its author never changed.
I'd lean toward putting both CI and pre-commit on 22.1.8 instead: CLANG_FORMAT_VERSION, CLANG_TIDY_VERSION, and pre-commit all land on one version, and nothing needs reformatting. Same "keep them in sync" outcome you asked for, without the churn. WDYT?
Move the cpp-linter and clang tool versions into the job's env block so they live in one place, and derive the clang-tidy --version major from CLANG_TOOLS_VERSION instead of repeating it.
| - name: Install cpp-linter and clang tools | ||
| if: github.event_name == 'pull_request' | ||
| run: python -m pip install "cpp-linter==${CPP_LINTER_VERSION}" "clang-format==${CLANG_TOOLS_VERSION}" "clang-tidy==${CLANG_TOOLS_VERSION}" |
Match the pip-upgrade step used in test.yml so the linter job doesn't rely on the runner's bundled pip.
| run: | | ||
| cpp-linter \ | ||
| --style=file \ | ||
| --tidy-checks='' \ | ||
| --version="${CLANG_TOOLS_VERSION%%.*}" \ | ||
| --files-changed-only=true \ | ||
| --lines-changed-only=true \ | ||
| --thread-comments=true \ | ||
| --ignore='build|cmake_modules|ci|src/iceberg/catalog/hive/gen-cpp' \ | ||
| --database=build \ | ||
| --verbosity=debug \ | ||
| --extra-arg='-std=c++23' \ | ||
| --extra-arg="-I${PWD}/src" \ | ||
| --extra-arg="-I${PWD}/build/src" \ | ||
| --extra-arg="-I${PWD}/build/_deps/sqlpp23-src/include" \ | ||
| --extra-arg='-I/usr/include/postgresql' \ | ||
| --extra-arg='-I/usr/include/mysql' \ | ||
| --extra-arg='-fno-builtin-std-forward_like' |
Decouple the two tools as CLANG_FORMAT_VERSION and CLANG_TIDY_VERSION so the format version can track .pre-commit-config.yaml (22.1.5) while clang-tidy uses 22.1.8, which is the nearest version published on PyPI. A comment marks the format version as the one to keep in sync.
| # clang-format is kept in sync with .pre-commit-config.yaml; clang-tidy is | ||
| # pinned separately because that version is not published on PyPI. | ||
| CLANG_FORMAT_VERSION: "22.1.5" | ||
| CLANG_TIDY_VERSION: "22.1.8" |
| if: github.event_name == 'pull_request' | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| python -m pip install "cpp-linter==${CPP_LINTER_VERSION}" "clang-format==${CLANG_FORMAT_VERSION}" "clang-tidy==${CLANG_TIDY_VERSION}" |
| run: | | ||
| cpp-linter \ | ||
| --style=file \ | ||
| --tidy-checks='' \ | ||
| --version="${CLANG_FORMAT_VERSION%%.*}" \ | ||
| --files-changed-only=true \ |
| python -m pip install --upgrade pip | ||
| python -m pip install "cpp-linter==${CPP_LINTER_VERSION}" "clang-format==${CLANG_FORMAT_VERSION}" "clang-tidy==${CLANG_TIDY_VERSION}" |
wgtmac
left a comment
There was a problem hiding this comment.
Thinking more about this, I just realized that we do not need to duplicate the format check in cpp-linter. That should make our lives easier.
| if: github.event_name == 'pull_request' | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| python -m pip install "cpp-linter==${CPP_LINTER_VERSION}" "clang-format==${CLANG_FORMAT_VERSION}" "clang-tidy==${CLANG_TIDY_VERSION}" |
There was a problem hiding this comment.
Do we need clang-format in this job at all? The pre-commit workflow already runs the pinned clang-format hook over all files on every PR. Keeping formatting there and making cpp-linter tidy-only would avoid duplicate checks and the version-sync problem. Could we drop CLANG_FORMAT_VERSION and the clang-format package here, and set --style to an empty string to disable format checks?
| cpp-linter \ | ||
| --style=file \ | ||
| --tidy-checks='' \ | ||
| --version="${CLANG_FORMAT_VERSION%%.*}" \ |
There was a problem hiding this comment.
Even with a tidy-only setup, deriving --version=22 would still bypass the pip pin. cpp-linter looks for clang-tidy-22 first, and the probe run selected /usr/bin/clang-tidy-22 at 22.1.2 instead of the installed 22.1.8. Could we point this to ${pythonLocation}/bin, or omit --version, so the pip-installed binary is actually used?
Drop clang-format from the cpp-linter job (pre-commit already runs the pinned clang-format hook on every PR) and point --version at the setup-python bin dir so clang-tidy resolves to the pip-installed 22.1.8 instead of a system clang-tidy-22. Adds a temporary probe to exercise CI; will be dropped.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
.github/workflows/cpp-linter.yml:106
- The workflow pins the top-level PyPI packages, but transitive dependencies can still drift over time, which may break CI reproducibility (and increases supply-chain surface). Consider installing from a locked requirements/constraints file (optionally with hashes) so the full dependency set is deterministic.
python -m pip install --upgrade pip
python -m pip install "cpp-linter==${CPP_LINTER_VERSION}" "clang-tidy==${CLANG_TIDY_VERSION}"
| // TEMP verification file — will be dropped before merge. | ||
| // Confirms cpp-linter runs clang-tidy with the pip-installed binary | ||
| // (via --version=${pythonLocation}/bin) after dropping clang-format. | ||
| namespace iceberg { |
Drop the probe added to exercise the tidy-only cpp-linter path in CI. CI confirmed clang-tidy resolves to the pip-installed 22.1.8 and reports the seeded violations.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
.github/workflows/cpp-linter.yml:101
- Because this workflow’s
pull_request.paths-ignoreincludes.github/**, a PR that only changes this workflow will not trigger the job. Now that the temporary probe file is gone, consider adding a temporaryworkflow_dispatch:trigger (or narrowing the ignore rules) so the final workflow-only change can be exercised in CI before merge.
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
if: github.event_name == 'pull_request'
with:
python-version: '3.13'
| cpp-linter \ | ||
| --style='' \ | ||
| --tidy-checks='' \ |
|
Nice work! Thanks @LuciferYang! |

What
cpp-linter/cpp-linter-actionis frozen at v2.15.1. Its v2.16 release pulled in an untrusted dependency and was blocked by ASF Infra (apache/infrastructure-actions#325), and the ASF gateway ignores newer versions. The pinned action still runs, but it can no longer be updated.How
The action is a thin wrapper around the
cpp-linterPyPI package, and the block does not touch that package. This installs it with pip and calls thecpp-linterCLI to run clang-tidy. Because it is arun:step and not auses:reference, it adds no new third-party action to the ASF allowlist. Thechecks-failedoutput and the Fail fast step stay as they were.Formatting is left to the pre-commit workflow, which already runs the pinned clang-format hook on every PR. So cpp-linter runs tidy-only:
--style='', and clang-format is not installed here. This keeps the clang-format version in one place instead of tracking it in two.--versionpoints at the setup-python bin directory so clang-tidy resolves to the pip-installed version rather than a system clang-tidy on PATH.I also looked at emitting SARIF and uploading it with
github/codeql-action/upload-sarif, which is allowed since it is agithub/*action. That route loses the thread comment and needs an extra conversion step, so calling the CLI directly stays closer to what we have now.Verified
CI ran the job on a temporary probe file seeded with a
NULLand a mis-named member. clang-tidy resolved to the pip-installed 22.1.8 (not a system clang-tidy-22), flagged both issues, and setchecks-failed, so the Fail fast step failed as expected. The probe has since been removed.This contribution was authored with assistance from Claude Opus 4.8, in line with the Iceberg guidelines for AI-assisted contributions (https://iceberg.apache.org/contribute/#guidelines-for-ai-assisted-contributions). All changes were reviewed and tested by the author.
Closes #336